#include "MgwGapProxy.h"

#include <stdint.h>
#include <list>
#include "Trace.h"
#include "GapGenerator.h"
#include "GAP.pb.h"

#include "GapEventConnectionInd.h"
#include "GapEventEchoResp.h"
#include "GapEventMediaTransmissionFinishedInd.h"
#include "GapEventMediaBearerSetupCfm.h"
#include "GapEventMediaBearerSetupRej.h"
#include "GapEventT8221Expired.h"
#include "GapEventT823Expired.h"

namespace tsc
{
namespace sgw
{

using namespace GatewayApplicationPart;
using ProtocolsCommon::PlayableItem;
using namespace tsc::common;

MgwGapProxy* MgwGapProxy::mInstance = 0;
std::map<uint32_t, GapGenerator*> MgwGapProxy::msConnectionIdGapGeneratorMap;

/*******************************************************************************
 *
 * Constructor
 *
 ******************************************************************************/
MgwGapProxy::MgwGapProxy(
		RequestHandler* rh,
		GeneratorEnvironment& ge) :
    mpReqHandler(rh),
    _ge(ge)
{
    INFO("Created MgwGapProxy in Serving Gateway");
} // MgwGapProxy::MgwGapProxy

/*******************************************************************************
 *
 * Destructor
 *
 ******************************************************************************/
MgwGapProxy::~MgwGapProxy()
{

}

//===================================================================
void
MgwGapProxy::init(RequestHandler* rh, GeneratorEnvironment& ge)
{
    if(!mInstance)
    {
        mInstance = new MgwGapProxy(rh, ge);
    }
}
//===================================================================
MgwGapProxy&
MgwGapProxy::instance()
{
    if(!mInstance)
    {
        TRACE_ERROR("Trying to get instance of a proxy before initialization");
    }
    return *mInstance;
}

//===================================================================
void
MgwGapProxy::handlePdu(Message*& pMessage)
{
    if(!pMessage)
    {
        TRACE(1, "Received NULL Message - ignoring");
        return;
    }

    unsigned int socketConnId = pMessage->getConnectionId();

    GapPdu* gapPdu = new GapPdu();
    if(!gapPdu->ParseFromArray(pMessage->getData(), pMessage->getDataSize()))
    {
        // TODO logging
        std::cout << "Unable to parse received data to GapPdu " << std::endl;
        DELETE_MESSAGE_P(pMessage);
        delete gapPdu;
        return;
    }
    if(!gapPdu->IsInitialized())
    {
        // TODO - logging
        std::cout << "Received invalid GapPdu, errorStr: " <<
                gapPdu->InitializationErrorString() << std::endl;
        DELETE_MESSAGE_P(pMessage);
        delete gapPdu;
        return;
    }

    handleGapPdu(gapPdu, socketConnId);

    DELETE_MESSAGE_P(pMessage);
}

//===================================================================
void
MgwGapProxy::handleGapPdu(GapPdu*& gapPdu, unsigned int socketConnId)
{

	common::EventData* event = 0;
	GapGenerator* generator = 0;
	uint32_t mgwId = gapPdu->mgwid();

	switch(gapPdu->pdunumber())
	{
        case GAP_PDU_CONNECT_IND:
        {
            GapConnectInd connInd;
            if(!connInd.ParseFromString(gapPdu->pdu()))
            {
                // TODO - logging
                std::cout << "Received invalid GapConnectInd " << std::endl;

                sendGapConnectionRej(mgwId,
                                     CAUSE_SYNTAX_ERROR);

                break;
            }

            if(mgwId == GAP_MGW_INITIAL_ID)
            {
                // MGW has connected for the first time
                generator = new GapGenerator(_ge, mgwId);
            }
            else
            {
                // try to find generator with received mgwId
                if(!GapGenerator::findGenerator(mgwId, generator))
                {
                    // didn't find generator so we need to create new one
                    generator = new GapGenerator(_ge, GAP_MGW_INITIAL_ID);
                }
            }

            event = new GapEventConnectionInd(connInd, socketConnId);

        }
        break;

        case GAP_PDU_ECHO_RES:
        {
            GapEchoResponse echoRes;
            if(!echoRes.ParseFromString(gapPdu->pdu()))
            {
                // TODO - logging
                std::cout << "Received invalid GapEchoResponse " << std::endl;
                break;
            }

            // try to find generator with received mgwId
            if(!GapGenerator::findGenerator(mgwId, generator))
            {
                // TODO logging
                std::cout << "Unable to find generator with id=0x"
                        << std::hex << mgwId << std::endl;
                break;
            }

            event = new GapEventEchoResp(echoRes);
        }
        break;

		case GAP_PDU_MEDIA_BEARER_SETUP_CFM:
		{
		    GapMediaBearerSetupConfirm bearerSetupCfm;
            if(!bearerSetupCfm.ParseFromString(gapPdu->pdu()))
            {
                // TODO - logging
                std::cout << "Received invalid GapMediaBearerSetupConfirm " << std::endl;
                break;
            }

            // try to find generator with received mgwId
            if(!GapGenerator::findGenerator(mgwId, generator))
            {
                // TODO logging
                std::cout << "Unable to find generator with id=0x"
                        << std::hex << mgwId << std::endl;
                break;
            }

            event = new GapEventMediaBearerSetupCfm(bearerSetupCfm);
		}
		break;

		case GAP_PDU_MEDIA_BEARER_SETUP_REJ:
		{
		    GapMediaBearerSetupReject bearerSetupRej;
            if(!bearerSetupRej.ParseFromString(gapPdu->pdu()))
            {
                // TODO - logging
                std::cout << "Received invalid GapMediaBearerSetupReject " << std::endl;
                break;
            }

            // try to find generator with received mgwId
            if(!GapGenerator::findGenerator(mgwId, generator))
            {
                // TODO logging
                std::cout << "Unable to find generator with id=0x"
                        << std::hex << mgwId << std::endl;
                break;
            }

            event = new GapEventMediaBearerSetupRej(bearerSetupRej);
		}
		break;

		case GAP_PDU_MEDIA_TRANSMISSION_END:
		{
		    GapMediaTransmissionFinishedInd transFinishedInd;
		    if(!transFinishedInd.ParseFromString(gapPdu->pdu()))
            {
                // TODO - logging
                std::cout << "Received invalid GapMediaTransmissionFinishedInd " << std::endl;
                break;
            }

            // try to find generator with received mgwId
            if(!GapGenerator::findGenerator(mgwId, generator))
            {
                // TODO logging
                std::cout << "Unable to find GapGenerator with id=0x"
                        << std::hex << mgwId << std::endl;
                break;
            }

            event = new GapEventMediaTransmissionFinishedInd(transFinishedInd);
		}
		break;

		case GAP_PDU_TIMER:
        {
            ProtocolsCommon::TimerExpired timerExp;
            if (!timerExp.ParseFromString(gapPdu->pdu()))
            {
                TRACE(1, STR("Received invalid TimerExpired message, errorStr: %s",
                             timerExp.InitializationErrorString().data()));

                break;
            }

            TRACE(4, STR("Received TimerExpired, mgwId=0x%x", mgwId));

            // try to find generator with received mgwId
            if(!GapGenerator::findGenerator(mgwId, generator))
            {
                // TODO logging
                std::cout << "Unable to find GapGenerator with id=0x"
                        << std::hex << mgwId << std::endl;
                break;
            }

            if(timerExp.timerid() == GAP_TIMER_ID_T8221)
            {
                event = new GapEventT8221Expired();
            }
            else if(timerExp.timerid() == GAP_TIMER_ID_T823)
            {
                event = new GapEventT823Expired();
            }
            else
            {
                TRACE(1, STR("Unexpected timerId=%u", timerExp.timerid()));
            }
        }
        break;

		default:
			// TODO - logging
			std::cout << "Received invalid GapPdu=0x" << std::hex << gapPdu->pdunumber() << std::endl;
	}

	if(event)
	{
		generator->injectEvent(event);

		// check if event was handled
		if(event)
		{
			std::cout << "Error, event(" << event->sigNo() << ") not handled" << std::endl;
			delete event;
		}
	}
	if(gapPdu)
	{
		delete gapPdu;
		gapPdu = 0;
	}

}

//===================================================================
bool
MgwGapProxy::sendGapConnectionRej(
    uint32_t                      mgwId,
    GatewayApplicationPart::Cause cause)
{
    GapConnectRej connRej;
    connRej.set_cause(cause);

    if(!connRej.IsInitialized())
    {
        // TODO logging
        std::cout << "Failed to encode GapConnectRej, mgwId=0x" << std::hex <<
                mgwId << ", errorStr: " << connRej.InitializationErrorString() << std::endl;
        return false;
    }

    if(!sendGapPdu(mgwId,
                   connRej.SerializeAsString(),
                   GAP_PDU_CONNECT_REJ))
    {
        // TODO logging
        std::cout << "Failed to send GapConnectRej, mgwId=0x" << std::hex <<
                mgwId << std::endl;
        return false;
    }
    return true;
}

//===================================================================
bool
MgwGapProxy::sendGapConnectionCfm(GapGenerator& gapGen)
{
    GapConnectCfm connCfm;
    connCfm.set_mgwid(gapGen.getMgwId());

    if(!connCfm.IsInitialized())
    {
        // TODO logging
        std::cout << "Failed to encode GapConnectCfm, mgwId=0x" << std::hex
                  << gapGen.getMgwId() << ", errorStr: "
                  << connCfm.InitializationErrorString() << std::endl;
        return false;
    }

    if(!sendGapPdu(gapGen.getMgwId(),
                   connCfm.SerializeAsString(),
                   GAP_PDU_CONNECT_CFM))
    {
        // TODO logging
        std::cout << "Failed to send GapConnectCfm, mgwId=0x" << std::hex
                  << gapGen.getMgwId() << std::endl;
        return false;
    }
    return true;
}


//===================================================================
bool
MgwGapProxy::sendGapEchoReq(GapGenerator& gapGen)
{
    GapEchoReq echoReq;

    if(!sendGapPdu(gapGen.getMgwId(),
                   echoReq.SerializeAsString(),
                   GAP_PDU_ECHO_REQ))
    {
        // TODO logging
        std::cout << "Failed to send GapEchoReq, mgwId=0x" << std::hex <<
                gapGen.getMgwId() << std::endl;
        return false;
    }
    return true;
}

//===================================================================
bool
MgwGapProxy::sendGapMediaBearerSetupReq(
    uint32_t                    connectionId,
    ProtocolsCommon::IpAddress* clientAddress,
    uint32_t                    clientMediaPort,
    uint32_t                    mediaItemId)
{
    GapGenerator* gen = 0;

    // this is the first connection oriented message send so first we
    // have to select MGW to which the message is send
    if(!selectMgw(connectionId, gen))
    {
        // TODO logging
        std::cout << "Unable to find MGW to handle request, connectionId=0x"
                  << std::hex << connectionId << std::endl;
        return false;
    }


    GapMediaBearerSetupRequest bearerSetupReq;
    bearerSetupReq.set_allocated_clientaddress(clientAddress);
    bearerSetupReq.set_clientmediaport(clientMediaPort);
    bearerSetupReq.set_mediaitemid(mediaItemId);
    bearerSetupReq.set_connectionid(connectionId);


    if(!bearerSetupReq.IsInitialized())
    {
        // TODO logging
        std::cout << "Failed to encode GapMediaBearerSetupRequest, mgwId=0x" << std::hex
                  << gen->getMgwId() << ", errorStr: "
                  << bearerSetupReq.InitializationErrorString() << std::endl;
        return false;
    }

    if(!sendGapPdu(gen->getMgwId(),
                   bearerSetupReq.SerializeAsString(),
                   GAP_PDU_MEDIA_BEARER_SETUP_REQ))
    {
        // TODO logging
        std::cout << "Failed to send GapMediaBearerSetupRequest, mgwId=0x" << std::hex <<
                gen->getMgwId() << std::endl;
        return false;
    }
    return true;
}

//===================================================================
bool
MgwGapProxy::sendGapMediaBearerRelease(uint32_t connectionId)
{
    GapGenerator* gen = 0;
    if(!findMgw(connectionId, gen))
    {
        // TODO logging
        std::cout << "There is no MGW assigned to connectionId=0x"
                  << std::hex << connectionId << ", can't send message" << std::endl;
        return false;
    }

    GapMediaBearerRelease bearerRelease;
    bearerRelease.set_connectionid(connectionId);

    if(!bearerRelease.IsInitialized())
    {
        // TODO logging
        std::cout << "Failed to encode GapMediaBearerRelease, mgwId=0x" << std::hex
                  << gen->getMgwId() << ", errorStr: "
                  << bearerRelease.InitializationErrorString() << std::endl;
        return false;
    }

    if(!sendGapPdu(gen->getMgwId(),
                   bearerRelease.SerializeAsString(),
                   GAP_PDU_MEDIA_BEARER_RELEASE))
    {
        // TODO logging
        std::cout << "Failed to send GapMediaBearerRelease, mgwId=0x" << std::hex <<
                gen->getMgwId() << std::endl;
        return false;
    }
    return true;

}

//===================================================================
bool
MgwGapProxy::sendGapPdu(
		uint32_t           mgwId,
		const std::string& msg,
		GapPduNumber       pduNum)
{
	GapPdu gapPdu;
	gapPdu.set_mgwid(mgwId);
	gapPdu.set_pdu(msg);
	gapPdu.set_pdunumber(pduNum);

	if(!gapPdu.IsInitialized())
	{
		// TODO print error
	    std::cout << "Failed to encode GapPdu with pduNum=0x" << std::hex <<
	            pduNum << std::endl;
		return false;
	}

	unsigned int socketConnId = 0;
    if(!getSocketId(mgwId, socketConnId))
    {
        TRACE(1, STR("Cannot find socketId for mgwId=0x%x(%u)",
                      mgwId, mgwId));
        return false;
    }

	std::string encodedGapPdu;
	gapPdu.SerializeToString(&encodedGapPdu);

	Message* pMessage = new Message(encodedGapPdu.data(),
	                                encodedGapPdu.size(),
	                                socketConnId);

	// send the message
    if(mpReqHandler->putRequestOnOutQueue(pMessage) == REQUEST_IS_ON_QUEUE)
    {
        // request handler claims ownership of the message
        pMessage = 0;
        return true;
    }
    return false;
}

//===================================================================
bool
MgwGapProxy::findMgw(uint32_t connectionId, GapGenerator*& gen)
{
    std::map<uint32_t, GapGenerator*>::iterator it;
    it = msConnectionIdGapGeneratorMap.find(connectionId);
    if(it != msConnectionIdGapGeneratorMap.end())
    {
        gen = it->second;
        return true;
    }
    return false;
}

//===================================================================
bool
MgwGapProxy::selectMgw(uint32_t connectionId, GapGenerator*& gen)
{
    if(findMgw(connectionId, gen))
    {
        return true;
    }

    // no luck, we need to search in global map of GapGenerators
    // and try to find one

    if(gGapGeneratorMap().empty())
    {
        return false;
    }
    // value 0 is only assigned first time this function is called
    // i.e at creation of this variable
    static uint32_t lastSelectedGeneratorId = 0;

    std::map<uint32_t, GapGenerator*>::iterator it;
    if(lastSelectedGeneratorId == 0)
    {
        it = gGapGeneratorMap().begin();
    }
    else
    {
        it = gGapGeneratorMap().find(lastSelectedGeneratorId);
        // move iterator since we want to start searching from next one
        // then previously selected
        ++it;
    }
    size_t maxAttempts = gGapGeneratorMap().size() * 2;
    size_t attempt = 0;
    while(attempt++ < maxAttempts)
    {
        if(it == gGapGeneratorMap().end())
        {
            it = gGapGeneratorMap().begin();
        }
        if(it->second->txEnabled())
        {
            // found suitable generator
            gen = it->second;
            // remember ID for the purpose of next search
            lastSelectedGeneratorId = it->first;

            // and finally store mapping in the map
            msConnectionIdGapGeneratorMap.insert(
                    std::pair<uint32_t, GapGenerator*>(connectionId, gen));

            return true;
        }
        ++it;
    }
    return false;
}

//===================================================================
void
MgwGapProxy::clearConnection(uint32_t connectionId)
{
    msConnectionIdGapGeneratorMap.erase(connectionId);
}

//===================================================================
void
MgwGapProxy::addConnIdMapping(
    uint32_t mgwId,
    unsigned int socketConnId)
{
    mSocketIdtoMgwIdMap.insert(
            std::pair<uint32_t, unsigned int>(mgwId, socketConnId));
}

//===================================================================
void
MgwGapProxy::clearConnIdMapping(uint32_t mgwId)
{
    mSocketIdtoMgwIdMap.erase(mgwId);
}

//===================================================================
bool
MgwGapProxy::getSocketId(
    uint32_t mgwId,
    unsigned int& socketId)
{
    std::map<uint32_t, unsigned int>::iterator it;
    it = mSocketIdtoMgwIdMap.find(mgwId);
    if(it != mSocketIdtoMgwIdMap.end())
    {
        socketId = it->second;
        return true;
    }
    return false;
}

common::RequestHandler*
MgwGapProxy::getRequestHandler()
{
    return mpReqHandler;
}

} // namespace sgw
} // namespace tsc
